#ifndef EMS_DEVICE_MODBUS_DEVICE_H
#define EMS_DEVICE_MODBUS_DEVICE_H

#include "device/DeviceBase.h"
#include "protocol/modbus/ModbusTCP.h"
#include "protocol/modbus/ModbusRTU.h"
#include "protocol/modbus/ProtocolManager.h"
#include "utils/Config.h"

#include <string>
#include <memory>
#include <map>

namespace ems {
namespace device {

class ModbusDevice : public DeviceBase {
public:
    ModbusDevice(const std::string& id, const std::string& name, const std::string& type);
    virtual ~ModbusDevice();

    bool initialize() override;
    bool connect() override;
    void disconnect() override;
    bool isConnected() const override;

    bool collectData(std::vector<DataValue>& data) override;
    bool writeData(const std::string& key, double value) override;

    bool setConnectionParams(const utils::DeviceConfig& config);
    bool setProtocolConfig(const std::string& protocolConfigPath);

private:
    struct ConnectionParams {
        bool isRtu;
        std::string address;
        int port;
        int slaveId;
        int timeout;

        struct RtuParams {
            std::string device;
            std::string serialType;
            int baudRate;
            int dataBits;
            int stopBits;
            char parity;
            int connectionDelay;
            bool reconnectOnTimeout;
            int reconnectTimeout;

            RtuParams()
                : baudRate(9600), dataBits(8), stopBits(1), parity('N'),
                  connectionDelay(0), reconnectOnTimeout(true), reconnectTimeout(2000) {}
        } rtu;

        ConnectionParams()
            : isRtu(false), port(502), slaveId(1), timeout(3000) {}
    };

    ConnectionParams connectionParams_;
    std::string protocolConfigPath_;

    std::unique_ptr<protocol::ModbusTCP> tcpClient_;
    std::unique_ptr<protocol::ModbusRTU> rtuClient_;
    std::unique_ptr<protocol::ProtocolManager> protocolManager_;

    protocol::ProtocolInterface* getClient();
    const protocol::ProtocolInterface* getClient() const;
    void resetClient();
    bool loadProtocolConfig();
    bool readDataPoints(std::vector<DataValue>& data);
    bool readDataPoint(const protocol::DataPoint& dataPoint, DataValue& dataValue);

    double convertRawData(const protocol::DataPoint& dataPoint, const std::vector<uint16_t>& rawData);
    protocol::RegisterType getFunctionCodeType(int functionCode);
};

}  // namespace device
}  // namespace ems

#endif  // EMS_DEVICE_MODBUS_DEVICE_H
